import { buildYup, YupBuilder } from 'schema-to-yup'
import { YupNumber} from 'schema-to-yup/src/types/number/index.js'
import  * as Yup from 'yup'
import { createNumberGuard } from "schema-to-yup/src/types/number/guard.js";
/**
 * Custom Handler qui override l'objet utilisé 
 * pour générer les champs numériques Yup
 * 
 * Celui-ci ajoute une fonction transform qui gère les cas :
 * 
 * - où le champ n'a jamais été défini 
 * - où le champ a été remis à vide
 */ 

class MyCustomNumberHandler extends YupNumber {
  get validatorInstance(){
    let result = this.validator.number()
    const nullable = this.value.nullable === true || this.value.null === true;
    if (nullable){
      result = result.transform(
        (value, originalValue) => (
          ['', undefined, false, NaN, null].includes(originalValue)
          ? null
          : Number(String(originalValue).replace(/,/g, '.'))
        )
      )
    }
    return result
  }
  static create(obj){
    return new MyCustomNumberHandler(obj);
  }
  static schemaEntryFor(obj) {
    return MyCustomNumberHandler.create(obj).createSchemaEntry();
  }
}

const defaultConfig = {
  logging: false,
  enable: { log: true, warn: true, error: true },
  typeHandlers: {
      number: (obj, config={}) => createNumberGuard(obj, config).verify() && MyCustomNumberHandler.create(obj).createSchemaEntry()
  }
}

/**
 * Injection key used to provide current form's data to child components
 *
 * https://vuejs.org/guide/components/provide-inject.html#working-with-symbol-keys
 */
export const formContextInjectionKey = Symbol('custom-current-form')

/**
 *
 * @param {*} yupSchema : A Yup.object() instance
 * @param {*} fieldName : A string
 * @returns true if the fieldName is required in the given schema
 */
export const isFieldRequired = (yupSchema, fieldName) => {
  return (
    yupSchema.fields[fieldName].tests.findIndex(
      ({ name }) => name === 'required'
    ) >= 0
  )
}

const findNodeInYupSchema = (yupSchema, fieldName) => {
  let node
  const schemaDescription = yupSchema.describe()
  if (fieldName.indexOf('.') == -1) {
    node = schemaDescription.fields[fieldName]
  } else {
    const fields = fieldName.split('.')
    node = schemaDescription
    for (let field of fields) {
      if (node.fields && (field in node.fields)) {
        node = node.fields[field];
      } else {
        node = {}
        break
      }
    }
  }
  return node
}

function isSchemaFieldRequired(fieldName) {
  const field = findNodeInYupSchema(this, fieldName)
  return field.tests.findIndex(({ name }) => name === 'required') >= 0
}

/**
 *
 * @param {*} jsonSchema Json Schema of the expected form schema
 * @returns a yup schema the schema is extended adding the original field properties to the yup schema
 */
export const buildYupSchema = (jsonSchema) => {
  // console.log('Building schema from json : ', jsonSchema)
  const yupSchema = buildYup(jsonSchema, defaultConfig)
  // On rattache une méthode pour tester si un champ rest requis
  yupSchema.isRequired = isSchemaFieldRequired
  // On rattache la définition des champs au schéma yup pour pouvoir accéder aux libellés description ...
  yupSchema.properties = jsonSchema.properties
  console.log("Resulting yup schema ")
  console.log(yupSchema)
  return yupSchema
}

const findNodeInJsonSchemaProps = (jsonSchemaProps, fieldName) => {
  let node
  if (fieldName.indexOf('.') == -1) {
    node = jsonSchemaProps[fieldName]
  } else {
    const fields = fieldName.split('.')
    const parentField = fields.shift()
    node = jsonSchemaProps[parentField]

    for (let field of fields) {
      if (field in node.properties) {
        node = node.properties[field]
      } else {
        node = {}
        break
      }
    }
  }
  return node
}

/**
 *
 * @param {*} yupSchema Extended Yup schema (generated with the
 * buildYupSchema here above)
 * @param {*} fieldName The name of the field we seek data for (can be a
 * path separated with dots like urssaf_data.birthdate)
 * @returns properties to be passed to Form field widgets
 */
export const getFieldData = (yupSchema, fieldName) => {
  let result = {}
  result['name'] = fieldName
  if (yupSchema.properties) {
    const node = findNodeInJsonSchemaProps(yupSchema.properties, fieldName)
    if (node == undefined || Object.keys(node).length === 0) {
      return result
    }
    if (node.title) {
      result['label'] = node.title
    }
    if (node.description) {
      result['description'] = node.description
    }
    if (node.readOnly){
      result['editable'] = false
    }

    result['required'] = yupSchema.isRequired(fieldName)
    if (node.format == 'date') {
      result['type'] = 'date'
    } else if (node.type == 'string') {
      result['type'] = 'text'
    } else if (node.type == 'array') {
      result['multiple'] = true
    }
  }
  return result
}

function collectDefaultsRec(properties) {
  const result = {}
  for (const [key, configuration] of Object.entries(properties)) {
    if (configuration.default) {
      result[key] = configuration.default
    } else if (configuration.type == 'object') {
      result[key] = collectDefaultsRec(configuration.properties)
    }
  }
  return result
}

/**
 * Return the list of default values
 *
 * @param {*} yupSchema Extended Yup schema
 */
export const getDefaults = (yupSchema) => {
  let result = {}
  if (yupSchema.properties) {
    result = collectDefaultsRec(yupSchema.properties)
  }
  return result
}

function scrollAndFocusFirstError(errors) {
  const allFields = Object.keys(errors)
  let selectors = allFields.map((fieldName) => `[name="${fieldName}"]`)
  selectors = selectors.join(',')
  const nodeList = document.querySelectorAll(selectors)
  if (nodeList.length > 0) {
    nodeList[0].focus()
    nodeList[0].parentNode.scrollIntoView()
  }
}

/**
 *
 */
export function getSubmitErrorCallback(emit, callback) {
  async function onSubmitError({ values, errors, results }) {
    console.log('Error in form')
    console.log(values) // current form values
    console.log(errors) // a map of field names and their first error message
    console.log(results) // a detailed map of field names and their validation results
    scrollAndFocusFirstError(errors)
    emit('error', values)
    if (callback) {
      callback(errors)
    }
  }
  return onSubmitError
}

/**
 * Renvoie un callback vue-validate qui en charge de la sauvegarde d'un modèle
 *
 * Le composant vue utilisant ce callback doit définir un emit de type 'saved'
 *
 * defineEmit(['saved', 'cancel'...])
 *
 * emit : fonction d'emit du composant
 * submitFunc : fonction de submit du modèle prend les valeurs du formulaire en paramètre et renvoie une Promise
 */
export function getSubmitModelCallback(emit, submitFunc) {
  async function onSubmit(values, actions) {
    return submitFunc(values).then(
      (savedModel) => {
        console.log(' + Model was added successfully')
        emit('saved', savedModel)
      },
      (errors) => {
        console.log(' + Errors were encountered')
        if (typeof errors === 'object') {
          console.log('   + Errors are set on the form')
          console.log(errors)
          actions.setErrors(errors)
        } else {
          alert(`Erreur inconnue rencontrée : ${errors}`)
        }
      }
    )
  }
  return onSubmit
}

// Définit globalement les messages d'erreur lors de la validation des schémas yup
// Liste des clés de configuration ici : https://github.com/jquense/yup/blob/master/src/locale.ts
Yup.setLocale({
  mixed: {
    default: 'Champ invalide',
    required: 'Champ requis',
    oneOf: 'Doit faire partie de : ${values}',
    notOneOf: 'Ne doit pas faire partie de : ${values}',
    notType: ({ path, type, value, originalValue }) => {
      if (type == "number") {
        return "Doit être un nombre";
      } else {
        console.log(type);
        return `Doit être de type ${type}`;
      }
    },
  },
  number: {
    min: 'Doit être supérieur à ${min}',
    max: 'Doit être inférieur à ${max}',
    lessThan: 'Doit être inférieur à ${less}',
    moreThan: 'Doit être supérieur à ${more}',
    positive: 'Doit être un nombre positif',
    negative: 'Doit être un nombre négatif',
    integer: 'Doit être un entier',
  },
  string: {
    length: 'Doit faire exactement ${length} caractères',
    min: 'Doit contenir au moins ${min} caractères',
    max: 'Ne doit pas dépasser ${max} caractères',
    email: 'Doit être une adresse e-mail valide',
    url: 'Doit être une url valide',
    lowercase: 'Ne doit contenir que des minuscules',
    uppercase: 'Ne doit contenir que des majuscules',
  },
  date: {
    min: 'Doit être après le ${min}',
    max: 'Doit être avant le ${max}',
  },
})
